﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Ajax.Utilities;
using System.Linq.Expressions;
using LinqExpression = System.Linq.Expressions.Expression;

namespace SimpleData.Infrastructure.JavaScript
{
	public class JavaScriptParser
	{

		/// <summary>
		/// 
		/// </summary>
		public bool IsDebuging
		{
			get
			{
#if DEBUG
				return true;
#else
				return false;
#endif
			}
		}


		public JavaScriptExpression Parse( string source )
		{
			JSParser parser = new JSParser( source );
			CodeSettings settings = parser.Settings;

			settings.BlocksStartOnSameLine = BlockStart.UseSource;
			settings.CollapseToLiteral = true;
			settings.EvalLiteralExpressions = true;
			settings.LocalRenaming = LocalRenaming.KeepAll;
			settings.MinifyCode = true;
			settings.PreserveFunctionNames = true;
			settings.RemoveUnneededCode = false;
			settings.ReorderScopeDeclarations = true;

			parser.CompilerError +=
				( sender, errorMsg ) =>
				{
				};

			LinqExpressionBuilder builder = new LinqExpressionBuilder();
			var exp = builder.Parse( parser.Parse( settings ) );

			return null;
		}
	}

	internal sealed class LinqExpressionBuilder : TreeVisitor
	{
		//private JavaScriptCodeContextStack stack;
		private JavaScriptCodeContext context;
		private SymbolDocumentInfo symbolDoc;

		public LinqExpressionBuilder()
		{
			//this.stack = new JavaScriptCodeContextStack();
		}

		public bool IsDebuging
		{ get; set; }

		public bool EmitDebugInfo
		{
			get { return this.IsDebuging && this.symbolDoc != null; }
		}
		
		private ExpressionSymbol DeclareSymbol( AstNode node, string name, ExpressionSymbolKind kind )
		{
			ExpressionSymbol symbol = new ExpressionSymbol( kind );
			this.context.Symbols.Add( name, symbol );

			if ( this.EmitDebugInfo )
			{
				Context jsContext = node.Context;
				this.PushStatement( LinqExpression.DebugInfo( this.symbolDoc, jsContext.StartLineNumber, jsContext.StartColumn, jsContext.EndLineNumber, jsContext.EndColumn ) );
			}

			return symbol;
		}

		public LinqExpression Parse( Block js )
		{
			// Inicia conversão
			js.Accept( this );

			// Se não tem nenhum contexto ou 
			// se sobrou mais de um contexto
			// então está errado
			if ( this.context == null || this.context.Parent != null )
				throw new InvalidOperationException( "" );

			return this.context.GetBlock();

			// Se não restar somente um block de expressões 
			// então há algo errado no código-fonte
			//if ( this.stack.Count != 1 )
			//    throw new InvalidOperationException( "Isso não deveria acontecer." );

			//var lastContext = this.stack.Pop();
			//return lastContext.GetBlock();

		}

		

		public LinqExpression ParseExpression( AstNode node, SymbolTable globals )
		{
			this.context = this.CreateContext();
			this.context.Symbols.Parent = globals;

			node.Accept( this );
			//this.Visit( nodes );
			//node.Accept( this );

			return this.context.GetBlock();
		}

		public override void Visit( Block node )
		{
			// Cria o primeiro block de código
			//this.stack.Push( new JavaScriptCodeContext() );
			//this.context = this.CreateContext();
			this.CreateContext();

			base.Visit( node );
		}

		public override void Visit( FunctionObject node )
		{
			var symbol = this.DeclareSymbol( node, node.Name, ExpressionSymbolKind.Function );
			base.Visit( node );

			// O contexto é criado pela própria árvore de expressões
			var functionContext = this.PopContext();

			//
			LambdaExpression function = LinqExpression.Lambda( functionContext.GetBlock() );
			symbol.Expression = function;
		}

		public override void Visit( ReturnNode node )
		{
			this.CreateContext();
			base.Visit( node );

			LinqExpression returnValue = this.PopContext().GetBlock();

			Type type = returnValue.GetReturnType();
			LabelTarget target = LinqExpression.Label( type );
			this.PushStatement( LinqExpression.Return( target, returnValue ) );
		}

		public override void Visit( BinaryOperator node )
		{
			base.Visit( node );
		}

		public override void Visit( ConstantWrapper node )
		{
			switch ( node.PrimitiveType )
			{
				case PrimitiveType.Boolean:
					this.PushStatement( LinqExpression.Constant( node.ToBoolean(), typeof( bool ) ) );
					break;
				case PrimitiveType.Number:
					var number = node.ToSmallestNumberType();
					this.PushStatement( LinqExpression.Constant( number, number.GetType() ) );
					break;
				case PrimitiveType.String:
					this.PushStatement( LinqExpression.Constant( node.ToString(), typeof( string ) ) );
					break;
				case PrimitiveType.Other:
					throw new NotImplementedException();
				case PrimitiveType.Null:
				default:
					this.PushStatement( LinqExpression.Constant( null, this.context.GetTypeFromLastExpression() ) );
					break;
			}

			base.Visit( node );
		}


		private JavaScriptCodeContext CreateContext()
		{
			this.context = new JavaScriptCodeContext( this.context );
			return this.context;
		}

		private JavaScriptCodeContext PopContext()
		{
			var self = this.context;
			this.context = self.Parent;
			return self;
		}

		private void PushStatement( LinqExpression exp )
		{
			this.context.Statements.Add( exp );
			//this.stack.Peak().Statements.Add( exp );
		}

		//private void PushStatement( LinqExpression exp )
		//{
		//    this.stack.Peak().Statements.Add( exp );
		//}

		//private LinqExpression PopStatement()
		//{
		//    return this.stack.Peak().Statements.RemoveLast();
		//}
	}

	public sealed class JavaScriptCodeContextStack : List<JavaScriptCodeContext>
	{

		public JavaScriptCodeContextStack()
		{ }

		public void Push( JavaScriptCodeContext context )
		{
			this.Add( context );
		}

		public JavaScriptCodeContext Pop()
		{
			JavaScriptCodeContext top = this.Peak();
			this.RemoveAt( this.Count - 1 );
			return top;
		}

		public JavaScriptCodeContext Peak()
		{
			return this[ this.Count - 1 ];
		}
	}

	//public interface ISymbolTable
	//{
	//    void GetSymbol( string name );
	//}

	public sealed class SymbolTable : Dictionary<string, ExpressionSymbol>
	{
		public SymbolTable()
			: this( null )
		{ }

		public SymbolTable( SymbolTable parent )
			: base( StringComparer.InvariantCultureIgnoreCase )
		{
			this.Parent = parent;
		}

		/// <summary>
		/// 
		/// </summary>
		public SymbolTable Parent
		{ get; set; }

		/// <summary>
		/// 
		/// </summary>
		/// <param name="name"></param>
		/// <returns></returns>
		public ExpressionSymbol GetRecursiveSymbol( string name )
		{
			ExpressionSymbol symbol = null;
			SymbolTable table = this;

			while ( table != null )
				if ( table.TryGetValue( name, out symbol ) )
					return symbol;
				else
					table = table.Parent;

			return symbol;
		}

		public IEnumerable<ParameterExpression> GetLocalVariables()
		{
			return this.Select( pair => pair.Value.Expression ).OfType<ParameterExpression>();
		}


		public bool TryGetValueAs<TExpression>( string name, out TExpression value )
			where TExpression : LinqExpression
		{
			ExpressionSymbol symbol;
			value = null;

			if ( this.TryGetValue( name, out symbol ) )
				value = symbol.Expression as TExpression;

			return value != null;
		}
	}

	public class JavaScriptCodeContext
	{
		public JavaScriptCodeContext( JavaScriptCodeContext parent )
		{
			this.Parent = parent;
			this.Statements = new List<LinqExpression>();

			if ( parent != null )
				this.Symbols = new SymbolTable( parent.Symbols );
			else
				this.Symbols = new SymbolTable();
		}

		public JavaScriptCodeContext Parent
		{ get; private set; }

		public SymbolTable Symbols
		{ get; private set; }

		//public Dictionary<string, ParameterExpression> Symbols
		//{ get; private set; }

		public List<LinqExpression> Statements
		{ get; private set; }


		// Evoluir esse GetBlock(), se tiver funções dentro do
		// do block, deve ser declarado como uma classe
		public LinqExpression GetBlock()
		{
			var vars = this.Symbols.GetLocalVariables();

			if ( vars.Any() )
				return LinqExpression.Block( vars, this.Statements );
			else if ( this.Statements.Count == 1 )
				return this.Statements[ 0 ];
			else
				return LinqExpression.Block( this.Statements );
		}

		public Type GetTypeFromLastExpression()
		{
			var lastExpression = this.Statements.LastOrDefault();

			if ( lastExpression != null )
				return lastExpression.GetReturnType();
			else
				return typeof( object );
		}

		
	}

	public sealed class ExpressionSymbol 
	{
		public ExpressionSymbol( ExpressionSymbolKind kind )
			: this( kind, null )
		{ }

		public ExpressionSymbol( ExpressionSymbolKind kind, LinqExpression value )
		{
			this.Kind = kind;
			this.Expression = value;
		}


		public ExpressionSymbolKind Kind
		{ get; private set; }

		public LinqExpression Expression
		{ get; set; }
	}

	public enum ExpressionSymbolKind
	{
		Variable = 0,
		Function = 1,
		Parameter = 2
	}
	
	internal static class JavaScriptCodeContextExtensions
	{

		public static readonly Type NumberType = ( Environment.Is64BitProcess ) ? typeof( long ) : typeof( int );

		public static object ToSmallestNumberType( this ConstantWrapper jsConst )
		{
			if ( jsConst.IsExactInteger )
				return Convert.ChangeType( jsConst.Value, NumberType );
			else
				return Convert.ToDouble( jsConst.Value );
		}

		public static TItem RemoveLast<TItem>( this IList<TItem> list )
		{
			int count = list.Count;

			if ( count > 0 )
			{
				int index = count--;
				try
				{
					return list[ index ];
				}
				finally
				{
					list.RemoveAt( index );
				}
			}
			else
				return default( TItem );
		}


		/// <summary>
		/// 
		/// </summary>
		/// <param name="exp"></param>
		/// <returns></returns>
		public static Type GetReturnType( this LinqExpression exp )
		{
			switch ( exp.NodeType )
			{
				case ExpressionType.AddAssign:
				case ExpressionType.AddAssignChecked:
				case ExpressionType.AndAssign:
				case ExpressionType.DivideAssign:
				case ExpressionType.ExclusiveOrAssign:
				case ExpressionType.LeftShiftAssign:
				case ExpressionType.ModuloAssign:
				case ExpressionType.MultiplyAssign:
				case ExpressionType.MultiplyAssignChecked:
				case ExpressionType.OrAssign:
				case ExpressionType.PowerAssign:
				case ExpressionType.RightShiftAssign:
				case ExpressionType.SubtractAssign:
				case ExpressionType.SubtractAssignChecked:
					return ( ( BinaryExpression )exp ).Type;

				case ExpressionType.Assign:
					return ( ( BinaryExpression )exp ).Left.GetReturnType();
					//return this.GetTypeFromExpression( ( ( BinaryExpression )exp ).Left );

				case ExpressionType.Constant:
					return ( ( ConstantExpression )exp ).Type;

				case ExpressionType.Increment:
				case ExpressionType.PostIncrementAssign:
				case ExpressionType.PostDecrementAssign:
				case ExpressionType.PreDecrementAssign:
				case ExpressionType.PreIncrementAssign:
					return ( ( UnaryExpression )exp ).Type;

				case ExpressionType.MemberAccess:
					return ( ( MemberExpression )exp ).Type;

				case ExpressionType.Parameter:
					return ( ( ParameterExpression )exp ).Type;
			}

			return typeof( object );
		}



		///////////////////////////////////

		public static TItem PenultOrDefault<TItem>( this IEnumerable<TItem> list )
		{
			return list.LastElementAtOrDefault( 1 );
		}

		public static TItem AntePenultOrDefault<TItem>( this IEnumerable<TItem> list )
		{
			return list.LastElementAtOrDefault( 2 );
		}


		public static TItem LastElementAtOrDefault<TItem>( this IEnumerable<TItem> list, int skip )
		{
			int count = list.Count() - 1;

			if ( count < skip )
				return default( TItem );
			else
				return list.ElementAt( count - skip );
		}
	}

	public class JavaScriptSource
	{

	}

	public class JavaScriptExpression
	{

		public JavaScriptExpression( string sourceCode, LinqExpression parsedCode )
		{
			this.SourceCode = sourceCode;
			this.ParsedCode = parsedCode;
		}

		public string SourceCode
		{ get; private set; }

		public LinqExpression ParsedCode
		{ get; private set; }
	}
}
